#include "database.h"
#include <QtCore/QtAlgorithms>
#include <QtCore/QDebug>

namespace acidone
{

Subject::Subject(const QString& name)
	:m_name(name)
{
}

QString Subject::name() const
{
	return m_name;
}

void Subject::setName(const QString &name)
{
	m_name = name;
}

Category::Category(const QString &name)
	:m_name(name)
{
}

QString Category::name() const
{
	return m_name;
}

void Category::setName(const QString &name)
{
	m_name = name;
}

Task::Task(QObject* parent)
	:QObject(parent)
	,m_priority(Normal)
	,m_subject(0)
	,m_creationDateTime(QDateTime::currentDateTime().toUTC())
{
}

QList<Category*> Task::categories() const
{
	return m_categories;
}

void Task::setCategories(QList<Category *> categories)
{
	if (m_categories != categories)
	{
		m_categories = categories;
		emit changed();
	}
}

void Task::setSingleCategory(Category *category)
{
	if (!m_categories.startsWith(category) ||
		m_categories.count() != 1)
	{
		m_categories.clear();
		m_categories.append(category);
		emit changed();
	}
}

void Task::addCategory(Category *category)
{
	if (!m_categories.contains(category))
	{
		m_categories.append(category);
		emit changed();
	}
}

void Task::removeCategory(Category *category)
{
	if (m_categories.removeOne(category))
		emit changed();
}

QString Task::caption() const
{
	return m_caption;
}

void Task::setCaption(const QString &caption)
{
	if (m_caption != caption)
	{
		m_caption = caption;
		emit changed();
	}
}

QDateTime Task::beginDateTime() const
{
	return m_beginDateTime;
}

void Task::setBeginDateTime(const QDateTime &beginDateTime)
{
	if (m_beginDateTime != beginDateTime)
	{
		m_beginDateTime = beginDateTime;
		emit changed();
	}
}

bool Task::isStarted() const
{
	return m_beginDateTime.isNull() ||
		   m_beginDateTime.toLocalTime() < QDateTime::currentDateTime();
}

QDateTime Task::endDateTime() const
{
	return m_endDateTime;
}

void Task::setEndDateTime(const QDateTime &endDateTime)
{
	if (m_endDateTime != endDateTime)
	{
		m_endDateTime = endDateTime;
		emit changed();
	}
}

Task::Priority Task::priority() const
{
	return m_priority;
}

void Task::setPriority(Priority priority)
{
	if (m_priority != priority)
	{
		m_priority = priority;
		emit changed();
	}
}

QString Task::notes() const
{
	return m_notes;
}

void Task::setNotes(const QString &notes)
{
	if (m_notes != notes)
	{
		m_notes = notes;
		emit changed();
	}
}

Subject* Task::subject() const
{
	return m_subject;
}

void Task::setSubject(Subject* subject)
{
	if (m_subject != subject)
	{
		m_subject = subject;
		emit changed();
	}
}

QDateTime Task::creationDateTime() const
{
	return m_creationDateTime;
}

void Task::setCreationDateTime(const QDateTime &dateTime)
{
	if (dateTime.isValid() && m_creationDateTime != dateTime)
	{
		m_creationDateTime = dateTime;
		emit changed();
	}
}

bool Task::isCompleted() const
{
	return m_completionDateTime.isNull();
}

void Task::complete()
{
	if (!isCompleted())
	{
		m_completionDateTime = QDateTime::currentDateTime().toUTC();
		emit changed();
	}
}

QDateTime Task::completionDateTime() const
{
	return m_completionDateTime;
}

void Task::setCompletionDateTime(const QDateTime &dateTime)
{
	if (m_completionDateTime != dateTime)
	{
		m_completionDateTime = dateTime;
		emit changed();
	}
}

Database::Database(QObject *parent)
	:QObject(parent)
{
}

Database::~Database()
{
	qDeleteAll(m_categories);
	qDeleteAll(m_subjects);
}

void Database::addTask(Task *task)
{
	if (this == task->parent())
	{
		qDebug() << "task is already in list";
		return;
	}

	m_tasks.append(task);
	task->setParent(this);
	connect(task, SIGNAL(changed()), this, SIGNAL(changed()));

	emit changed();
}

void Database::removeTask(Task *task)
{
	if (m_tasks.removeOne(task))
	{
		task->disconnect();
		delete task;
		emit changed();
	}
	else
	{
		qDebug() << "task not found in list";
	}
}

Database::TaskList Database::tasks() const
{
	return m_tasks;
}

struct TasksByCategoryPred
{
	Category* m_category;
	TasksByCategoryPred(Category* category):m_category(category) {}
	bool operator()(const Task& task)
	{
		return task.categories().contains(m_category);
	}
};

Database::TaskList Database::tasksByCategory(Category *category) const
{
	return tasksByCondition(TasksByCategoryPred(category));
}

struct TasksBySubjectPred
{
	Subject* m_subject;
	TasksBySubjectPred(Subject* subject):m_subject(subject) {}
	bool operator()(const Task& task)
	{
		return task.subject() == m_subject;
	}
};

Database::TaskList Database::tasksBySubject(Subject *subject) const
{
	return tasksByCondition(TasksBySubjectPred(subject));
}

Category* Database::addCategory(const QString &name)
{
	m_categories.append(new Category(name));
	emit changed();
	return m_categories.last();
}

Category* Database::findCategory(const QString &name)
{
	foreach(Category* category, m_categories)
		if (category->name() == name)
			return category;
	return 0;
}

void Database::removeCategory(Category* category)
{
	if (m_categories.removeOne(category))
	{
		TaskList tasks = tasksByCategory(category);
		foreach(Task* task, tasks)
			task->removeCategory(category);
		delete category;
		emit changed();
	}
	else
	{
		qDebug() << "no category to remove";
	}
}

Database::Categories Database::categories() const
{
	return m_categories;
}

QStringList Database::categoryNames() const
{
	QStringList names;
	foreach(Category* category, m_categories)
		names.append(category->name());
	return names;
}

Subject* Database::addSubject(const QString &name)
{
	m_subjects.append(new Subject(name));
	emit changed();
	return m_subjects.last();
}

Subject* Database::findSubject(const QString &name)
{
	foreach(Subject* subject, m_subjects)
		if (subject->name() == name)
			return subject;
	return 0;
}

void Database::removeSubject(Subject* subject)
{
	Q_ASSERT(subject != 0);

	if (m_subjects.removeOne(subject))
	{
		TaskList tasks = tasksBySubject(subject);
		foreach(Task* task, tasks)
			task->setSubject(0);
		delete subject;
		emit changed();
	}
	else
	{
		qDebug() << "no subject to remove";
	}
}

Database::Subjects Database::subjects() const
{
	return m_subjects;
}

QStringList Database::subjectNames() const
{
	QStringList names;
	foreach(Subject* subject, m_subjects)
		names.append(subject->name());
	return names;
}

}
